package io.gitee.zhangbinhub.admin.oauth.component

import io.gitee.zhangbinhub.acp.core.CommonTools
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.security.oauth2.core.*
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService
import org.springframework.stereotype.Component
import org.springframework.util.SerializationUtils
import java.io.*
import java.nio.charset.Charset


@Component
class AuthTokenService @Autowired constructor(private val redisConnectionFactory: RedisConnectionFactory) :
    OAuth2AuthorizationService {
    private val tokenPrefixId = "acp_cloud_oauth_token_id_"
    private val tokenPrefixValue = "acp_cloud_oauth_token_value_"
    private val tokenPrefixClientId = "acp_cloud_oauth_token_client_id_"
    private val charSet = Charset.forName(CommonTools.getDefaultCharset())

    override fun save(authorization: OAuth2Authorization) {
        val serializeTokenId = serializeKey("$tokenPrefixId${authorization.id}")
        val serializeTokenValue = serializeKey("$tokenPrefixValue${authorization.accessToken.token.tokenValue}")
        val serializeClientId = serializeKey("$tokenPrefixClientId${authorization.registeredClientId}")
        val authInfo = serialize(authorization)
        val expiresMillis = authorization.accessToken.token.expiresAt.let { expiration ->
            if (expiration != null) {
                expiration.toEpochMilli() - System.currentTimeMillis()
            } else {
                0
            }
        }
        redisConnectionFactory.connection.let { connection ->
            connection.openPipeline()
            connection.set(serializeTokenId, authInfo)
            connection.set(serializeTokenValue, authInfo)
            connection.sAdd(serializeClientId, authInfo)
            connection.pExpire(serializeTokenId, expiresMillis)
            connection.pExpire(serializeTokenValue, expiresMillis)
            connection.closePipeline()
        }
    }

    override fun remove(authorization: OAuth2Authorization) {
        val serializeTokenId = serializeKey("$tokenPrefixId${authorization.id}")
        val serializeTokenValue = serializeKey("$tokenPrefixValue${authorization.accessToken.token.tokenValue}")
        val serializeClientId = serializeKey("$tokenPrefixClientId${authorization.registeredClientId}")
        redisConnectionFactory.connection.let { connection ->
            connection.openPipeline()
            connection.get(serializeTokenId)
            connection.del(serializeTokenId, serializeTokenValue)
            connection.closePipeline().let { result ->
                result[0] as? ByteArray
            }?.let { access ->
                connection.sRem(serializeClientId, access)
            }
        }
    }

    fun removeByToken(token: String) {
        findByToken(token)?.apply { remove(this) }
    }

    fun removeTokensByClientIdAndUserName(clientId: String, userName: String) {
        findTokensByClientIdAndUserName(clientId, userName).forEach { remove(it) }
    }

    override fun findById(id: String?): OAuth2Authorization? =
        redisConnectionFactory.connection.get(serializeKey("$tokenPrefixId$id"))?.let {
            deserialize(it)
        }

    fun findByToken(token: String?): OAuth2Authorization? = findByToken(token, null)

    override fun findByToken(token: String?, tokenType: OAuth2TokenType?): OAuth2Authorization? =
        redisConnectionFactory.connection.get(serializeKey("$tokenPrefixValue$token"))?.let {
            deserialize(it)
        }

    fun findTokensByClientIdAndUserName(clientId: String, userName: String): Collection<OAuth2Authorization> =
        findTokensByClientId(clientId).filter { it.principalName == userName }

    fun findTokensByClientId(clientId: String): Collection<OAuth2Authorization> =
        serializeKey("$tokenPrefixClientId$clientId").let { serializeClientId ->
            redisConnectionFactory.connection.sMembers(serializeClientId)?.let { byteArrayList ->
                redisConnectionFactory.connection.let { connection ->
                    connection.openPipeline()
                    val authorizationList = byteArrayList.map { byteArray ->
                        deserialize(byteArray).let { authorization ->
                            if (authorization.accessToken.isExpired) {
                                connection.sRem(serializeClientId, byteArray)
                                null
                            } else {
                                authorization
                            }
                        }
                    }
                    connection.closePipeline()
                    authorizationList.filterNotNull()
                }
            } ?: emptySet()
        }

    /**
     * 序列化key
     */
    fun serializeKey(key: String): ByteArray = key.toByteArray(charSet)

    /**
     * 序列化
     */
    @Throws(OAuth2AuthenticationException::class)
    fun serialize(oAuth2Authorization: OAuth2Authorization): ByteArray =
        SerializationUtils.serialize(oAuth2Authorization) ?: throw OAuth2AuthenticationException("序列化失败")

    /**
     * 反序列化
     */
    @Throws(IOException::class, ClassNotFoundException::class, OAuth2AuthorizationException::class)
    fun deserialize(serializeData: ByteArray): OAuth2Authorization =
        (SerializationUtils.deserialize(serializeData) as? OAuth2Authorization)
            ?: throw OAuth2AuthorizationException(
                OAuth2Error(OAuth2ErrorCodes.INVALID_TOKEN), "反序列化失败"
            )
}